package genetic_algorithm;

/**
 * The Gene class attempts to create a generic Gene object for the Genetic Algorithm package.
 * This allows problems to be encoded however the user sees fit.
 * @author pborrego
 */
public class Gene<T> {
    protected T dna;       // The dna value of the Gene block.
    protected T[] nucleotides;    // The possible values of all Gene blocks.

    /**
     * Creates a new Gene object
     * @param value -- value of this Gene
     */
    public Gene(T dna) {
        this.dna = dna;
        this.nucleotides = null;
    }

    /**
     * Creates a new random Gene object from all the possible Gene values.
     * @param coding -- all possible Gene values.
     */
    public Gene(T[] nucleotides) {
        this.nucleotides = nucleotides;
        this.dna = this.randomNucleotide();
    }

    /**
     * Gets the Gene dna
     * @return -- the dna value of this Gene.
     */
    public T getDna() {
        return dna;
    }

    /**
     * Sets the Gene dna
     * @param dna -- the new dna value of the Gene.
     */
    public void setDna(T code) {
        this.dna = code;
    }

    /**
     * Returns an array of all the possible dna values of this Gene.
     * @return -- an array of all possible dna values.
     */
    public T[] getNucleotides() {
        return nucleotides;
    }

    /**
     * Sets the nucleotides possible for generating random Gene values. Codes must be set for flip to work.
     * @param nucleotides -- an array holding all possible Gene values.
     */
    public void setNucleotides(T[] codes) {
        this.nucleotides = codes;
    }

    /**
     * This randomly changes the value of the Gene based on all the possible Gene values.
     * Nothing happens if nucleotides is set or nucleotides only has one value. If these two conditions are met
     * then after flip the value of the Gene is guaranteed to be different.
     */
    public void flip() {
        if(this.nucleotides == null) return;
        if(this.nucleotides.length == 1) return;

        T tmp = this.dna;
        while(this.dna.equals(tmp)) {
            this.dna = this.randomNucleotide();
        }
    }

    /**
     * Returns a clone of the Gene with a random nucleotide.
     * If nucleotides is null then the current dna code is set as the clone's dna
     * @return -- A new Gene.
     */
    @Override
    public Gene<T> clone() {
        if(this.nucleotides == null)
            return new Gene<T>(this.dna);

        return new Gene<T>(this.nucleotides);
    }

    /**
     * A utility to get a random Gene block from the chromosome.
     * @return -- Returns a random index position or -1 if there are no Gene blocks set.
     */
    private T randomNucleotide() {
        if(this.nucleotides == null) return null;

        return this.nucleotides[Double.valueOf(Math.random() * this.nucleotides.length).intValue()];
    }
}
